{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Supply Network Design 2\n",
    "\n",
    "## Objective and Prerequisites\n",
    "\n",
    "Take your supply chain network design skills to the next level in this example. We’ll show you how – given a set of factories, depots, and customers – you can use mathematical optimization to determine which depots to open or close in order to minimize overall costs.\n",
    "\n",
    "This model is example 20 from the fifth edition of Model Building in Mathematical Programming, by H. Paul Williams on pages 275-276 and 332-333.\n",
    "\n",
    "This example is of beginning difficulty; we assume that you know Python and have some knowledge of the Gurobi Python API and building mathematical optimization models.\n",
    "\n",
    "**Download the Repository** <br />\n",
    "You can download the repository containing this and other examples by clicking [here](https://github.com/Gurobi/modeling-examples/archive/master.zip). \n",
    "\n",
    "---\n",
    "## Problem Description\n",
    "\n",
    "In this problem, we have six end customers, each with a known demand for a product.  Customer demand can be satisfied from a set of six depots, or directly from a set of two factories.  Each depot can support a maximum volume of product moving through it, and each factory can produce a maximum amount of product.  There are known costs associated with transporting the product, from a factory to a depot, from a depot to a customer, or from a factory directly to a customer. This extension provides the opportunity to choose which four of the six possible depots to open.  It also provides an option of expanding capacity at one specific depot.\n",
    "\n",
    "Our supply network has two factories, in Liverpool and Brighton, that produce a product.  Each has a maximum production capacity:\n",
    "\n",
    "| Factory | Supply (tons) |\n",
    "| --- | --- |\n",
    "| Liverpool | 150,000 |\n",
    "| Brighton |  200,000 |\n",
    "\n",
    "The product can be shipped from a factory to a set of six depots.  Each depot has a maximum throughput.  Depots don't produce or consume the product; they simply pass the product through to customers.\n",
    "\n",
    "| Depot | Throughput (tons) |\n",
    "| --- | --- |\n",
    "| Newcastle | 70,000 |\n",
    "| Birmingham | 50,000 |\n",
    "| London | 100,000 |\n",
    "| Exeter | 40,000 |\n",
    "| Bristol | 30,000 |\n",
    "| Northampton | 25,000 |\n",
    "\n",
    "We can actually only choose four of the six depots to open.  Opening a depot has a cost:\n",
    "\n",
    "| Depot | Cost to open |\n",
    "| --- | --- |\n",
    "| Newcastle | 10,000 |\n",
    "| Exeter | 5,000 |\n",
    "| Bristol | 12,000 |\n",
    "| Northampton | 4,000 |\n",
    "\n",
    "(Note that the description in the book talks about the cost of opening Bristol or Northampton, and the savings from closing Newcastle or Exeter, but these are simply different ways of phrasing the same choice).\n",
    "\n",
    "We also have the option of expanding the capacity at Birmingham by 20,000 tons, for a cost of \\$3000.\n",
    "\n",
    "Our network has six customers, each with a given demand.\n",
    "\n",
    "| Customer | Demand (tons) |\n",
    "| --- | --- |\n",
    "| C1 | 50,000 |\n",
    "| C2 | 10,000 |\n",
    "| C3 | 40,000 |\n",
    "| C4 | 35,000 |\n",
    "| C5 | 60,000 |\n",
    "| C6 | 20,000 |\n",
    "\n",
    "Shipping costs are given in the following table (in dollars per ton).  Columns are source cities and rows are destination cities.  Thus, for example, it costs $1 per ton to ship the product from Liverpool to London.  A '-' in the table indicates that that combination is not possible, so for example it is not possible to ship from the factory in Brighton to the depot in Newcastle.\n",
    "\n",
    "| To | Liverpool | Brighton | Newcastle | Birmingham | London | Exeter | Briston | Northhampton\n",
    "| --- | --- | --- | --- | --- | --- | --- | --- | --- |\n",
    "| Depots |\n",
    "| Newcastle   | 0.5 |   - |\n",
    "| Birmingham  | 0.5 | 0.3 |\n",
    "| London      | 1.0 | 0.5 |\n",
    "| Exeter      | 0.2 | 0.2 |\n",
    "| Bristol     | 0.6 | 0.4 |\n",
    "| Northampton | 0.4 | 0.3 |\n",
    "| Customers |\n",
    "| C1 | 1.0 | 2.0 |   - | 1.0 |   - |   - | 1.2 |   - |\n",
    "| C2 |   - |   - | 1.5 | 0.5 | 1.5 |   - | 0.6 | 0.4 |\n",
    "| C3 | 1.5 |   - | 0.5 | 0.5 | 2.0 | 0.2 | 0.5 |   - |\n",
    "| C4 | 2.0 |   - | 1.5 | 1.0 |   - | 1.5 |   - | 0.5 |\n",
    "| C5 |   - |   - |   - | 0.5 | 0.5 | 0.5 | 0.3 | 0.6 |\n",
    "| C6 | 1.0 |   - | 1.0 |   - | 1.5 | 1.5 | 0.8 | 0.9 |\n",
    "\n",
    "The questions to be answered: (i) Which four depots should be opened? (ii) Should Birmingham be expanded? (iii) Which depots should be used to satisfy customer demand?\n",
    "\n",
    "---\n",
    "## Model Formulation\n",
    "\n",
    "### Sets and Indices\n",
    "\n",
    "$f \\in \\text{Factories}=\\{\\text{Liverpool}, \\text{Brighton}\\}$\n",
    "\n",
    "$d \\in \\text{Depots}=\\{\\text{Newcastle}, \\text{Birmingham}, \\text{London}, \\text{Exeter}, \\text{Bristol}, \\text{Northampton}\\}$\n",
    "\n",
    "$c \\in \\text{Customers}=\\{\\text{C1}, \\text{C2}, \\text{C3}, \\text{C4}, \\text{C5}, \\text{C6}\\}$\n",
    "\n",
    "$\\text{Cities} = \\text{Factories} \\cup \\text{Depots} \\cup \\text{Customers}$\n",
    "\n",
    "### Parameters\n",
    "\n",
    "$\\text{cost}_{s,t} \\in \\mathbb{R}^+$: Cost of shipping one ton from source $s$ to destination $t$.\n",
    "\n",
    "$\\text{supply}_f \\in \\mathbb{R}^+$: Maximum possible supply from factory $f$ (in tons).\n",
    "\n",
    "$\\text{through}_d \\in \\mathbb{R}^+$: Maximum possible flow through depot $d$ (in tons).\n",
    "\n",
    "$\\text{demand}_c \\in \\mathbb{R}^+$: Demand for goods at customer $c$ (in tons).\n",
    "\n",
    "$\\text{opencost}_d \\in \\mathbb{R}^+$: Cost of opening depot $d$ (in dollars).\n",
    "\n",
    "### Decision Variables\n",
    "\n",
    "$\\text{flow}_{s,t} \\in \\mathbb{N}^+$: Quantity of goods (in tons) that is shipped from source $s$ to destionation $t$.\n",
    "\n",
    "$\\text{open}_{d} \\in [0,1]$: Is depot $d$ open?\n",
    "\n",
    "$\\text{expand} \\in [0,1]$: Should Birmingham be expanded?\n",
    "\n",
    "\n",
    "### Objective Function\n",
    "\n",
    "- **Cost**: Minimize total shipping costs plus costs of opening depots.\n",
    "\n",
    "\\begin{equation}\n",
    "\\text{Minimize} \\quad Z = \\sum_{(s,t) \\in \\text{Cities} \\times \\text{Cities}}{\\text{cost}_{s,t}*\\text{flow}_{s,t}} +\n",
    "                          \\sum_{{d} \\in \\text{Depots}}{\\text{opencost}_d*\\text{open}_d} +\n",
    "                          3000 * \\text{expand}\n",
    "\\end{equation}\n",
    "\n",
    "### Constraints\n",
    "\n",
    "- **Factory output**: Flow of goods from a factory must respect maximum capacity.\n",
    "\n",
    "\\begin{equation}\n",
    "\\sum_{t \\in \\text{Cities}}{\\text{flow}_{f,t}} \\leq \\text{supply}_{f} \\quad \\forall f \\in \\text{Factories}\n",
    "\\end{equation}\n",
    "\n",
    "- **Customer demand**: Flow of goods must meet customer demand.\n",
    "\n",
    "\\begin{equation}\n",
    "\\sum_{s \\in \\text{Cities}}{\\text{flow}_{s,c}} = \\text{demand}_{c} \\quad \\forall c \\in \\text{Customers}\n",
    "\\end{equation}\n",
    "\n",
    "- **Depot flow**: Flow into a depot equals flow out of the depot.\n",
    "\n",
    "\\begin{equation}\n",
    "\\sum_{s \\in \\text{Cities}}{\\text{flow}_{s,d}} = \n",
    "\\sum_{t \\in \\text{Cities}}{\\text{flow}_{d,t}}\n",
    "\\quad \\forall d \\in \\text{Depots}\n",
    "\\end{equation}\n",
    "\n",
    "- **Depot capacity (all but Birmingham)**: Flow into a depot must respect depot capacity, and is only allowed if the depot is open.\n",
    "\n",
    "\\begin{equation}\n",
    "\\sum_{s \\in \\text{Cities}}{\\text{flow}_{s,d}} \\leq \\text{through}_{d} * \\text{open}_{d}\n",
    "\\quad \\forall d \\in \\text{Depots} - \\text{Birmingham}\n",
    "\\end{equation}\n",
    "\n",
    "- **Depot capacity (Birmingham)**: Flow into Birmingham must respect depot capacity, which may have been expanded.\n",
    "\n",
    "\\begin{equation}\n",
    "\\sum_{s \\in \\text{Cities}} \\text{flow}_{s,\\text{Birmingham}} \\leq \\text{through}_{\\text{Birmingham}} + 20000 * \\text{expand}\n",
    "\\end{equation}\n",
    "\n",
    "- **Open depots**: At most 4 open depots (no choice for Birmingham or London).\n",
    "\n",
    "\\begin{equation}\n",
    "\\sum_{d \\in \\text{Depots}}{\\text{open}_{d}} \\leq 4\n",
    "\\end{equation}\n",
    "\n",
    "\\begin{equation}\n",
    "\\text{open}_{\\text{Birmingham}} = \\text{open}_{\\text{London}} = 1\n",
    "\\end{equation}\n",
    "\n",
    "---\n",
    "## Python Implementation\n",
    "\n",
    "We import the Gurobi Python Module and other Python libraries."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%pip install gurobipy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "\n",
    "import gurobipy as gp\n",
    "from gurobipy import GRB\n",
    "\n",
    "# tested with Python 3.11 & Gurobi 11.0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Input Data\n",
    "We define all the input data for the model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create dictionaries to capture factory supply limits, depot throughput limits, cost of opening depots, and customer demand.\n",
    "\n",
    "supply = dict({'Liverpool': 150000,\n",
    "               'Brighton': 200000})\n",
    "\n",
    "through = dict({'Newcastle': 70000,\n",
    "                'Birmingham': 50000,\n",
    "                'London': 100000,\n",
    "                'Exeter': 40000,\n",
    "                'Bristol': 30000,\n",
    "                'Northampton': 25000})\n",
    "\n",
    "opencost = dict({'Newcastle': 10000,\n",
    "                 'Birmingham': 0,\n",
    "                 'London': 0,\n",
    "                 'Exeter': 5000,\n",
    "                 'Bristol': 12000,\n",
    "                 'Northampton': 4000})\n",
    "\n",
    "demand = dict({'C1': 50000,\n",
    "               'C2': 10000,\n",
    "               'C3': 40000,\n",
    "               'C4': 35000,\n",
    "               'C5': 60000,\n",
    "               'C6': 20000})\n",
    "\n",
    "# Create a dictionary to capture shipping costs.\n",
    "\n",
    "arcs, cost = gp.multidict({\n",
    "    ('Liverpool', 'Newcastle'): 0.5,\n",
    "    ('Liverpool', 'Birmingham'): 0.5,\n",
    "    ('Liverpool', 'London'): 1.0,\n",
    "    ('Liverpool', 'Exeter'): 0.2,\n",
    "    ('Liverpool', 'Bristol'): 0.6,\n",
    "    ('Liverpool', 'Northampton'): 0.4,\n",
    "    ('Liverpool', 'C1'): 1.0,\n",
    "    ('Liverpool', 'C3'): 1.5,\n",
    "    ('Liverpool', 'C4'): 2.0,\n",
    "    ('Liverpool', 'C6'): 1.0,\n",
    "    ('Brighton', 'Birmingham'): 0.3,\n",
    "    ('Brighton', 'London'): 0.5,\n",
    "    ('Brighton', 'Exeter'): 0.2,\n",
    "    ('Brighton', 'Bristol'): 0.4,\n",
    "    ('Brighton', 'Northampton'): 0.3,\n",
    "    ('Brighton', 'C1'): 2.0,\n",
    "    ('Newcastle', 'C2'): 1.5,\n",
    "    ('Newcastle', 'C3'): 0.5,\n",
    "    ('Newcastle', 'C5'): 1.5,\n",
    "    ('Newcastle', 'C6'): 1.0,\n",
    "    ('Birmingham', 'C1'): 1.0,\n",
    "    ('Birmingham', 'C2'): 0.5,\n",
    "    ('Birmingham', 'C3'): 0.5,\n",
    "    ('Birmingham', 'C4'): 1.0,\n",
    "    ('Birmingham', 'C5'): 0.5,\n",
    "    ('London', 'C2'): 1.5,\n",
    "    ('London', 'C3'): 2.0,\n",
    "    ('London', 'C5'): 0.5,\n",
    "    ('London', 'C6'): 1.5,\n",
    "    ('Exeter', 'C3'): 0.2,\n",
    "    ('Exeter', 'C4'): 1.5,\n",
    "    ('Exeter', 'C5'): 0.5,\n",
    "    ('Exeter', 'C6'): 1.5,\n",
    "    ('Bristol', 'C1'): 1.2,\n",
    "    ('Bristol', 'C2'): 0.6,\n",
    "    ('Bristol', 'C3'): 0.5,\n",
    "    ('Bristol', 'C5'): 0.3,\n",
    "    ('Bristol', 'C6'): 0.8,\n",
    "    ('Northampton', 'C2'): 0.4,\n",
    "    ('Northampton', 'C4'): 0.5,\n",
    "    ('Northampton', 'C5'): 0.6,\n",
    "    ('Northampton', 'C6'): 0.9\n",
    "})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Model Deployment\n",
    "\n",
    "We create a model and the variables. The 'flow' variables simply capture the amount of product that flows along each allowed path between a source and destination.  The 'open' variable capture decisions about which depots to open.  The 'expand' variable captures the choice of whether to expand Birmingham.  Objective coefficients are provided here, so we don't need to provide an optimization objective later."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Using license file c:\\gurobi\\gurobi.lic\n"
     ]
    }
   ],
   "source": [
    "model = gp.Model('SupplyNetworkDesign2')\n",
    "\n",
    "depots = through.keys()\n",
    "flow = model.addVars(arcs, obj=cost, name=\"flow\")\n",
    "open = model.addVars(depots, obj=opencost, vtype=GRB.BINARY, name=\"open\")\n",
    "expand = model.addVar(obj=3000, vtype=GRB.BINARY, name=\"expand\")\n",
    "\n",
    "open['Birmingham'].lb = 1\n",
    "open['London'].lb = 1\n",
    "model.objcon = -(opencost['Newcastle'] + opencost['Exeter']) # Phrased as 'savings from closing'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Our first constraints require the total flow along arcs leaving a factory to be at most as large as the supply capacity of that factory."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Production capacity limits\n",
    "\n",
    "factories = supply.keys()\n",
    "factory_flow = model.addConstrs((gp.quicksum(flow.select(factory, '*')) <= supply[factory]\n",
    "                                 for factory in factories), name=\"factory\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Our next constraints require the total flow along arcs entering a customer to be equal to the demand from that customer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Customer demand\n",
    "\n",
    "customers = demand.keys()\n",
    "customer_flow = model.addConstrs((gp.quicksum(flow.select('*', customer)) == demand[customer]\n",
    "                                  for customer in customers), name=\"customer\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Our final constraints relate to depots.  The first constraints require that the total amount of product entering the depot must equal the total amount leaving."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Depot flow conservation\n",
    "\n",
    "depot_flow = model.addConstrs((gp.quicksum(flow.select(depot, '*')) == gp.quicksum(flow.select('*', depot))\n",
    "                               for depot in depots), name=\"depot\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The second set limits the product passing through the depot to be at most equal the throughput of that deport, or 0 if the depot isn't open."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Depot throughput\n",
    "\n",
    "all_but_birmingham = list(set(depots) - set(['Birmingham']))\n",
    "\n",
    "depot_capacity = model.addConstrs((gp.quicksum(flow.select(depot, '*')) <= through[depot]*open[depot]\n",
    "                                   for depot in all_but_birmingham), name=\"depot_capacity\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The capacity constraint for Birmingham is different.  The depot is always open, but we have the option of expanding its capacity."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "birmingham_capacity = model.addConstr(gp.quicksum(flow.select('*', 'Birmingham')) <= through['Birmingham'] +\n",
    "                                      20000*expand, name=\"birmingham_capacity\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, there's a limit of at most 4 open depots"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Depot count\n",
    "\n",
    "depot_count = model.addConstr(open.sum() <= 4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We now optimize the model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Gurobi Optimizer version 9.1.0 build v9.1.0rc0 (win64)\n",
      "Thread count: 4 physical cores, 8 logical processors, using up to 8 threads\n",
      "Optimize a model with 21 rows, 49 columns and 119 nonzeros\n",
      "Model fingerprint: 0x140cc3a9\n",
      "Variable types: 42 continuous, 7 integer (7 binary)\n",
      "Coefficient statistics:\n",
      "  Matrix range     [1e+00, 1e+05]\n",
      "  Objective range  [2e-01, 1e+04]\n",
      "  Bounds range     [1e+00, 1e+00]\n",
      "  RHS range        [4e+00, 2e+05]\n",
      "Presolve removed 0 rows and 2 columns\n",
      "Presolve time: 0.00s\n",
      "Presolved: 21 rows, 47 columns, 113 nonzeros\n",
      "Variable types: 42 continuous, 5 integer (5 binary)\n",
      "\n",
      "Root relaxation: objective 1.740000e+05, 17 iterations, 0.00 seconds\n",
      "\n",
      "    Nodes    |    Current Node    |     Objective Bounds      |     Work\n",
      " Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time\n",
      "\n",
      "*    0     0               0    174000.00000 174000.000  0.00%     -    0s\n",
      "\n",
      "Explored 0 nodes (17 simplex iterations) in 0.02 seconds\n",
      "Thread count was 8 (of 8 available processors)\n",
      "\n",
      "Solution count 1: 174000 \n",
      "\n",
      "Optimal solution found (tolerance 1.00e-04)\n",
      "Best objective 1.740000000000e+05, best bound 1.740000000000e+05, gap 0.0000%\n"
     ]
    }
   ],
   "source": [
    "model.optimize()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## Analysis\n",
    "\n",
    "The product demand from all of our customers can be satisfied for a total cost of $\\$174,000$ by opening a depot in Northampton, closing the depot in Newcastle, and expanding the depot in Birmingham:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "List of open depots: ['Birmingham', 'London', 'Exeter', 'Northampton']\n",
      "Expand Birmingham\n"
     ]
    }
   ],
   "source": [
    "print('List of open depots:', [d for d in depots if open[d].x > 0.5])\n",
    "if expand.x > 0.5:\n",
    "    print('Expand Birmingham')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>From</th>\n",
       "      <th>To</th>\n",
       "      <th>Flow</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <td>Liverpool</td>\n",
       "      <td>Exeter</td>\n",
       "      <td>40000.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <td>Liverpool</td>\n",
       "      <td>C1</td>\n",
       "      <td>50000.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <td>Liverpool</td>\n",
       "      <td>C6</td>\n",
       "      <td>20000.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <td>Brighton</td>\n",
       "      <td>Birmingham</td>\n",
       "      <td>70000.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <td>Brighton</td>\n",
       "      <td>London</td>\n",
       "      <td>10000.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <td>Brighton</td>\n",
       "      <td>Northampton</td>\n",
       "      <td>25000.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <td>Birmingham</td>\n",
       "      <td>C2</td>\n",
       "      <td>10000.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <td>Birmingham</td>\n",
       "      <td>C4</td>\n",
       "      <td>10000.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <td>Birmingham</td>\n",
       "      <td>C5</td>\n",
       "      <td>50000.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <td>London</td>\n",
       "      <td>C5</td>\n",
       "      <td>10000.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <td>Exeter</td>\n",
       "      <td>C3</td>\n",
       "      <td>40000.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <td>Northampton</td>\n",
       "      <td>C4</td>\n",
       "      <td>25000.0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "         From           To     Flow\n",
       "    Liverpool       Exeter  40000.0\n",
       "    Liverpool           C1  50000.0\n",
       "    Liverpool           C6  20000.0\n",
       "     Brighton   Birmingham  70000.0\n",
       "     Brighton       London  10000.0\n",
       "     Brighton  Northampton  25000.0\n",
       "   Birmingham           C2  10000.0\n",
       "   Birmingham           C4  10000.0\n",
       "   Birmingham           C5  50000.0\n",
       "       London           C5  10000.0\n",
       "       Exeter           C3  40000.0\n",
       "  Northampton           C4  25000.0"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "product_flow = pd.DataFrame(\n",
    "    [{\"From\": arc[0], \"To\": arc[1], \"Flow\": flow[arc].x} for arc in arcs if flow[arc].x > 1e-6]\n",
    ")\n",
    "product_flow.index=[''] * len(product_flow)\n",
    "product_flow"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## References\n",
    "\n",
    "H. Paul Williams, Model Building in Mathematical Programming, fifth edition.\n",
    "\n",
    "Copyright © 2020 Gurobi Optimization, LLC"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
